package cn.feizhu.aimarket.service.impl;

import cn.feizhu.aimarket.constant.FileTypeEnum;
import cn.feizhu.aimarket.exception.ErrorCode;
import cn.feizhu.aimarket.exception.ThrowUtils;
import cn.feizhu.aimarket.mapper.BookMapper;
import cn.feizhu.aimarket.model.domain.Book;
import cn.feizhu.aimarket.model.dto.app.AppAddRequest;
import cn.feizhu.aimarket.model.vo.BookVO;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.feizhu.aimarket.model.domain.App;
import cn.feizhu.aimarket.service.AppService;
import cn.feizhu.aimarket.mapper.AppMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.scheduling.annotation.Async;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
* @author wsdsb
* @description 针对表【app(应用表)】的数据库操作Service实现
* @createDate 2025-10-12 13:03:42
*/
@Service
@Slf4j
public class AppServiceImpl extends ServiceImpl<AppMapper, App>
    implements AppService{

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private BookMapper bookMapper;

    private static final String APP_LIST_CACHE_KEY = "app:list:cache";
    private static final String APP_LIST_LOCK_KEY = "app:list:lock";
    private static final Random random = new Random();


    @Override
    public Long createApp(AppAddRequest appAddRequest, Long userId) {
        // 参数验证
        ThrowUtils.throwIf(appAddRequest == null, ErrorCode.PARAMS_ERROR, "请求参数不能为空");
        ThrowUtils.throwIf(userId == null || userId <= 0, ErrorCode.PARAMS_ERROR, "用户ID无效");

        String appName = appAddRequest.getAppName();
        String systemPrompt = appAddRequest.getSystemPrompt();

        // 验证必填字段
        ThrowUtils.throwIf(StrUtil.isBlank(appName), ErrorCode.PARAMS_ERROR, "应用名称不能为空");
        ThrowUtils.throwIf(StrUtil.isBlank(systemPrompt), ErrorCode.PARAMS_ERROR, "系统提示词不能为空");

        // 创建App实体
        App app = new App();
        app.setAppName(appName.trim());
        app.setSystemPrompt(systemPrompt.trim());
        app.setUserId(userId);

        // 设置可选字段
        if (StrUtil.isNotBlank(appAddRequest.getCover())) {
            app.setCover(appAddRequest.getCover().trim());
        }
        if (StrUtil.isNotBlank(appAddRequest.getType())) {
            app.setType(appAddRequest.getType().trim());
        }
        if (StrUtil.isNotBlank(appAddRequest.getDescription())) {
            app.setDescription(appAddRequest.getDescription().trim());
        }
        if (appAddRequest.getPriority() != null) {
            app.setPriority(appAddRequest.getPriority());
        }

        // 设置文件生成类型，默认为md
        String fileGenType = appAddRequest.getFileGenType();
        if (StrUtil.isBlank(fileGenType)) {
            app.setFileGenType(FileTypeEnum.MARKDOWN.getCode());
        } else {
            // 验证文件类型是否有效
            ThrowUtils.throwIf(!FileTypeEnum.isValidFileType(fileGenType),
                ErrorCode.PARAMS_ERROR, "无效的文件生成类型");
            app.setFileGenType(fileGenType);
        }

        // 保存到数据库
        boolean saved = this.save(app);
        ThrowUtils.throwIf(!saved, ErrorCode.SYSTEM_ERROR, "应用创建失败");

        return app.getId();
    }


    /**
     * 获取公开应用列表
     * 优先从Redis缓存获取，如果缓存为空则异步加载数据并返回null
     * 使用Redisson分布式锁防止缓存击穿
     */
    @Override
    public List<App> listApps() {
        log.debug("开始获取App列表");

        try {
            // 1. 先尝试从缓存获取
            String cachedData = stringRedisTemplate.opsForValue().get(APP_LIST_CACHE_KEY);
            if (StrUtil.isNotBlank(cachedData)) {
                List<App> appList = objectMapper.readValue(cachedData, new TypeReference<List<App>>() {});
                log.debug("从缓存获取到App列表，数量: {}", appList.size());
                return appList;
            }

            // 2. 缓存为空，触发异步加载
            log.info("缓存为空，触发异步加载");
            asyncLoadAppListToCache();

            // 3. 立即返回null，避免阻塞用户请求
            return null;
        } catch (Exception e) {
            log.error("获取App列表失败", e);
            // 发生异常时也触发异步加载
            asyncLoadAppListToCache();
            return null;
        }
    }

    //TODO 根据appId和userId加载书籍列表
    // 这里BookVO里面属性都是基本类型，所以直接转换了
    @Override
    public List<BookVO> loadBooksVO(Long appId, long userId) {
        List<Book> books = bookMapper.loadBooks(appId, userId);
        return books.stream().map(book -> {
            BookVO bookVO = new BookVO();
            BeanUtils.copyProperties(book, bookVO);
            return bookVO;
        }).toList();
    }

    /**
     * 异步加载App列表到缓存
     * 使用Redisson分布式锁防止重复加载
     */
    @Async
    public void asyncLoadAppListToCache() {
        RLock lock = redissonClient.getLock(APP_LIST_LOCK_KEY);
        try {
            // 尝试获取锁，最多等待1秒，锁定10秒
            if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
                try {
                    // 双重检查，防止重复加载
                    String cachedData = stringRedisTemplate.opsForValue().get(APP_LIST_CACHE_KEY);
                    if (StrUtil.isNotBlank(cachedData)) {
                        log.debug("其他线程已加载缓存，跳过");
                        return;
                    }

                    // 从数据库加载数据
                    List<App> appList = queryAppListFromDatabase();

                    // 序列化并缓存到Redis，设置30天+随机1-2天的过期时间
                    String jsonData = objectMapper.writeValueAsString(appList);
                    long expireTime = 30 + random.nextInt(3); // 30-32天
                    stringRedisTemplate.opsForValue().set(APP_LIST_CACHE_KEY, jsonData, expireTime, TimeUnit.DAYS);

                    log.info("成功加载App列表到缓存，数量: {}, 过期时间: {}天", appList.size(), expireTime);
                } finally {
                    lock.unlock();
                }
            } else {
                log.debug("获取分布式锁失败，可能其他线程正在加载");
            }
        } catch (Exception e) {
            log.error("异步加载App列表到缓存失败", e);
        }
    }

    /**
     * 从数据库查询公开应用列表
     * 这里可以根据业务需求添加更多查询条件
     */
    private List<App> queryAppListFromDatabase() {
        try {
            QueryWrapper<App> queryWrapper = new QueryWrapper<>();
            // 只有优先级大于等于1的相当于公开的才可以展示
            queryWrapper.ge("priority", 1);
            // 按使用次数优先排序，然后按创建时间排序
            // 注意：对于索引 idx_priority_create_time_uses_cnt(priority, create_time, uses_cnt)
            // 会因为最左前缀匹配失效，所以不行
            queryWrapper.orderByDesc("uses_cnt", "create_time");

            List<App> appList = this.list(queryWrapper);
            log.info("从数据库查询到App列表，数量: {}", appList != null ? appList.size() : 0);
            return appList != null ? appList : new ArrayList<>();
        } catch (Exception e) {
            log.error("从数据库查询App列表失败", e);
            return new ArrayList<>();
        }
    }

}




